package main

import "fmt"

/*
Go语言面向对象编程之结构体(struct)

	1. Go并不是一个纯面向对象的编程语言。Go语言并没有提供类（class），Go语言采用更灵活的“结构体”替代了“类“；
	2. Go语言没有继承和多态，但是Go语言可以【通过匿名字段】实现继承，【通过接口】实现多态， Go语言中通过结构体的内嵌再配合接口比
       面向对象具有更高的扩展性和灵活性。在Go语言中学习主要学习结构体（struct）、方法（method）、接口（interface）；
	3. 结构体是值类型。结构体作为函数参数，若复制一份传递到函数中，在函数中对参数进行修改，不会影响到实际参数，可以通过指针来实现修改；
	4. 只有当结构体实例化时，才会真正地分配内存。也就是必须实例化后才能使用结构体的字段
	5. Go 还提供了 struct tags，它可以用来指定 struct 中每个字段的元信息。为什么 Go 语言中需要使用 struct tags，
       以及 struct tags 的使用场景和优势，可见：https://juejin.cn/post/7209102613345566776
*/
func main() {

	// 1. var 声明方式实例化结构体
	var teacher Teacher
	fmt.Println(teacher)
	fmt.Printf(" var 声明方式实例化结构体: %T, %v, %q \n", teacher, teacher, teacher)
	// 初始化方式为：对象.属性 = 值
	teacher.age = 28
	teacher.name = "steven"
	teacher.sex = 1
	fmt.Println(teacher)
	fmt.Printf(" var 声明方式初始化后结构体: %T, %v, %q \n", teacher, teacher, teacher)

	// 2. 变量简短声明格式实例化结构体
	teacher2 := Teacher{}
	fmt.Println(teacher2)
	fmt.Printf("变量简短声明方式实例化结构体: %T, %v, %q \n", teacher2, teacher2, teacher2)
	teacher2.age = 82
	teacher2.name = "nevets"
	teacher2.sex = 2
	fmt.Println(teacher2)
	fmt.Printf("变量简短声明方式初始化结构体: %T, %v, %q \n", teacher2, teacher2, teacher2)

	// 3. 实例化的同时初始化。初始化方式为：属性：值,
	teacher3 := Teacher{
		name: "qinxh",
		age:  26,
		sex:  1,
	}
	fmt.Println(teacher3)
	fmt.Printf("实例化的同时初始化: %T, %v, %q \n", teacher3, teacher3, teacher3)

	// 4. 实例化的同时初始化。初始化时不写属性名，按属性的顺序写属性值
	teacher4 := Teacher{
		"qinxha",
		23,
		1}
	fmt.Println(teacher4)
	fmt.Printf("实例化的同时初始化: %T, %v, %p \n", teacher4, teacher4, teacher4)

	// 5. 使用内置函数new()对结构体进行实例化，结构体实例化后形成【指针类型的结构体】，new()内置函数会分配内存。
	emp := new(Teacher)
	fmt.Printf("new(Teacher): %T, %v, %p \n", emp, emp, emp)
	(*emp).name = "DDD"
	(*emp).sex = 1
	(*emp).age = 15
	fmt.Printf("new(Teacher): %T, %v, %p \n", emp, emp, emp)
	// 语法糖写法
	emp.name = "TTT"
	emp.age = 20
	emp.sex = 1
	fmt.Printf("new(Teacher): %T, %v, %p \n", emp, emp, emp)

	// 6. 结构体是值类型。值类型拷贝是深拷贝，深拷贝就是为新的对象分配了内存。引用类型拷贝是浅拷贝，浅拷贝只是复制了对象的指针。结构体可以使用
	//	  【new()函数实例化对象】或【指针】来实现浅拷贝

	// 深拷贝（teacher5修改值后teacher4属性值并没有改变）
	teacher5 := teacher4
	fmt.Printf(" 深拷贝: %T, %v, %p \n", teacher4, teacher4, &teacher4)
	fmt.Printf(" 深拷贝: %T, %v, %p \n", teacher5, teacher5, &teacher5)
	teacher5.sex = 2
	teacher5.age = 111
	teacher5.name = "深拷贝"
	fmt.Printf(" 深拷贝: %T, %v, %p \n", teacher4, teacher4, &teacher4)
	fmt.Printf(" 深拷贝: %T, %v, %p \n", teacher5, teacher5, &teacher5)
	// 指针实现浅拷贝（teacher6修改值后teacher4属性值改变了）
	teacher6 := &teacher4
	fmt.Printf(" 浅拷贝: %T, %v, %p \n", teacher4, teacher4, &teacher4)
	fmt.Printf(" 浅拷贝: %T, %v, %p \n", teacher6, teacher6, &teacher6)
	teacher6.name = "浅拷贝"
	(*teacher6).age = 111
	fmt.Printf(" 浅拷贝: %T, %v, %p \n", teacher4, teacher4, &teacher4)
	fmt.Printf(" 浅拷贝: %T, %v, %p \n", teacher6, teacher6, &teacher6)
	// new()函数实例化的浅拷贝（emp2修改值后emp属性值改变了）
	emp2 := emp
	fmt.Printf("new()函数实例化的浅拷贝: %T, %v, %p \n", emp, emp, emp)
	fmt.Printf("new()函数实例化的浅拷贝: %T, %v, %p \n", emp2, emp2, emp2)
	emp2.sex = 2
	emp2.age = 30
	emp2.name = "DTDTDT"
	fmt.Printf("new()函数实例化的浅拷贝: %T, %v, %p \n", emp, emp, emp)
	fmt.Printf("new()函数实例化的浅拷贝: %T, %v, %p \n", emp2, emp2, emp2)
	// 结构体对象通过函数修改属性值(emp2和emp属性值并没有改变，说明结构体是值类型)。
	// TIPS：此时的*emp 就是对象，而不是对象的指针，因为emp本身是通过内置函数new 创建出来的指针类型的结构体
	change(*emp)
	fmt.Printf("结构体对象传递: %T, %v, %p \n", emp, emp, emp)
	fmt.Printf("结构体对象传递: %T, %v, %p \n", emp2, emp2, emp2)
	change(*emp2)
	fmt.Printf("结构体对象传递: %T, %v, %p \n", emp, emp, emp)
	fmt.Printf("结构体对象传递: %T, %v, %p \n", emp2, emp2, emp2)
	// 结构体对象指针通过函数修改属性值(emp2和emp属性值改变)
	changeByPointer(emp)
	fmt.Printf("通过指针传递: %T, %v, %p \n", emp, emp, emp)
	fmt.Printf("通过指针传递: %T, %v, %p \n", emp2, emp2, emp2)

	// 7. 结构体对象作为返回值
	t := returnStruct()
	t2 := returnStruct()
	fmt.Printf("结构体对象作为返回值t: %T, %v, %p \n", t, t, &t)
	fmt.Printf("结构体对象作为返回值t2: %T, %v, %p \n", t2, t2, &t2)
	t.name = "t的name更改"
	fmt.Printf("结构体对象作为返回值t: %T, %v, %p \n", t, t, &t)
	fmt.Printf("结构体对象作为返回值t2: %T, %v, %p \n", t2, t2, &t2)

	// 8. 结构体对象指针作为返回值
	t3 := returnStruct2()
	t4 := returnStruct2()
	fmt.Printf("结构体对象指针作为返回值t3: %T, %v, %p \n", t3, t3, t3)
	fmt.Printf("结构体对象指针作为返回值t4: %T, %v, %p \n", t4, t4, t4)
	t3.name = "返回对象指针t3更改了"
	fmt.Printf("结构体对象指针作为返回值t3: %T, %v, %p \n", t3, t3, t3)
	fmt.Printf("结构体对象指针作为返回值t4: %T, %v, %p \n", t4, t4, t4)
}

/*
结构体的定义只是一种内存布局的描述，【只有当结构体实例化时，才会真正分配内存】。因此只有在定义结构体并实例化后才能使用结构体
实例化就是根据结构体定义的格式创建一份与格式一致的内存区域。结构体每个实例的内存是完全独立的。
	1. 结构体格式：
		type 结构体名 struct {
			成员属性1 类型1
			成员属性2 类型2
			成员属性3, 成员属性4 类型3
			...
		}
	2. TIPS:
		• 结构体名是标识结构体的名称，在同一个包内不能重复
		• 结构体的属性也叫字段，必须唯一
		• 同类型的成员属性可以写在一行
*/

type Teacher struct {
	name string
	age  int8
	sex  byte
}

/*
将结构体对象作为参数
*/
func change(teacher Teacher) {

	fmt.Printf("函数体中的teacher修改前: %T, %v, %p \n", teacher, teacher, &teacher)
	teacher.name = "改变了"
	teacher.age = 100
	teacher.sex = 0
	fmt.Printf("函数体中的teacher修改后: %T, %v, %p \n", teacher, teacher, &teacher)
}

/*
将结构体对象指针作为参数
*/
func changeByPointer(teacherPtr *Teacher) {

	fmt.Printf("函数体中指针: %T, %v, %p \n", teacherPtr, teacherPtr, teacherPtr)
	teacherPtr.name = "通过指针修改"
	teacherPtr.age = 30
	teacherPtr.sex = 3
}

/*
返回结构体对象
*/
func returnStruct() Teacher {

	t := Teacher{
		"qinxh",
		12,
		2,
	}
	fmt.Printf("返回结构体对象函数内部: %T, %v, %p \n", t, t, &t)

	return t
}

/*
返回结构体对象指针
*/
func returnStruct2() (teacherPtr *Teacher) {

	t := Teacher{
		"返回结构体对象指针",
		112,
		2,
	}
	fmt.Printf("返回结构体对象指针函数内部: %T, %v, %p \n", t, t, &t)

	t2 := &t
	fmt.Printf("返回结构体对象指针函数内部: %T, %v, %p \n", t2, t2, t2)

	return t2
}
